package dao

import (
	"context"
	"errors"

	"bmember/pkg/rpc"

	"github.com/google/uuid"

	"bmember/internal/model"

	admin_rpc "bmember/api/pb/client/admin"
	open_rpc "bmember/api/pb/client/open/api/open/v1"
	sms_rpc "bmember/api/pb/client/sms"
	"bmember/internal/object"
)

// BilinVendorDao 比邻系统内部相关调用
type BilinVendorDao interface {
	// PermissionCheck admin 后台权限校验
	PermissionCheck(ctx context.Context, token, perRouter string) (bool, *object.AdminToken, error)
	// OpenPlatformLogin open, 小程序平台侧登陆
	OpenPlatformLogin(ctx context.Context, appId, code string, platform int8) (openId string, unionId string, err error)
	// AddressSearch 地址搜索
	AddressSearch(ctx context.Context, address string, lat, lng float64) ([]*object.SearchAddressInfo, error)
	// LocationToAddress 坐标转地址
	LocationToAddress(ctx context.Context, lat, lng float64) (*object.LocatedAddressInfo, error)
	// AddressToLocation 地址转坐标解析
	AddressToLocation(ctx context.Context, address, region string) (*object.AddressParseInfo, error)
	// GetMinaEncryptedMobile 手机号解密
	GetMinaEncryptedMobile(ctx context.Context, appId, openId, encryptedData, iv string, platform int8) (mobile string, countryCode string, err error)
	// VerifyMobileCode 短信验证
	VerifyMobileCode(ctx context.Context, mobile, code, codeType string) (bool, error)
	// SendMobileCode 发送短信验证码
	SendMobileCode(ctx context.Context, mobile, codeType string) error
}

type vDaoImpl struct {
	*DaoBase
	adminPool *rpc.Pool
	smsPool   *rpc.Pool
	openPool  *rpc.Pool
	name      string
}

var vDao BilinVendorDao

// InitBilinVendorDao 初始化 dao 层
func InitBilinVendorDao(adminPool, smsPool, openPool *rpc.Pool) {
	vDao = &vDaoImpl{
		DaoBase:   daoBase,
		adminPool: adminPool,
		smsPool:   smsPool,
		openPool:  openPool,
		name:      "card_dao",
	}
}

func GetBilinVendorDao() BilinVendorDao {
	return vDao
}

// PermissionCheck admin 后台权限校验
func (dao *vDaoImpl) PermissionCheck(ctx context.Context, token, perRouter string) (bool, *object.AdminToken, error) {
	req := &admin_rpc.TokenPermissionCheckReq{
		Token:   token,
		StoreId: 0,
		Router:  perRouter,
	}

	conn, err := dao.adminPool.Get(ctx)
	if err != nil {
		return false, nil, err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := admin_rpc.NewBilinPermissionClient(conn.ClientConn)
	res, err := client.PermissionCheckByToken(ctx, req)
	if err != nil {
		return false, nil, err
	}
	if !res.HasPermission {
		return false, nil, nil
	}
	t := &object.AdminToken{
		BrandId: res.BrandId,
		EmpId:   res.EmpId,
		EmpName: res.EmpName,
	}
	return true, t, nil
}

// SendMobileCode 发送短信验证码
func (dao *vDaoImpl) SendMobileCode(ctx context.Context, mobile, codeType string) error {
	if mobile == "" || codeType == "" {
		return errors.New("codeType, mobile is null")
	}
	var rID string
	rIDi := ctx.Value("_requestID")
	if rIDi != nil {
		rID = rIDi.(string)
	} else {
		uid, _ := uuid.NewUUID()
		rID = uid.String()
	}
	body := map[string]interface{}{
		"request_id":   rID,
		"phone":        mobile,
		"appid":        "bmember",
		"message_type": codeType,
	}
	bodyB, err := json.Marshal(body)
	if err != nil {
		return err
	}
	return dao.nsq.Publish("gsms_verify_send", bodyB)
}

// VerifyMobileCode 短信验证
func (dao *vDaoImpl) VerifyMobileCode(ctx context.Context, mobile, code, codeType string) (bool, error) {
	req := &sms_rpc.VerifyReq{
		Phone:       mobile,
		AppId:       "bmember",
		MessageType: codeType,
		Code:        code,
	}
	conn, err := dao.smsPool.Get(ctx)
	if err != nil {
		return false, err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := sms_rpc.NewSMSClient(conn.ClientConn)
	resp, err := client.CodeVerify(ctx, req)
	if err != nil {
		return false, err
	}
	return resp.VerifyResult, nil
}

// OpenPlatformLogin open, 小程序平台侧登陆
func (dao *vDaoImpl) OpenPlatformLogin(ctx context.Context, appId, code string, platform int8) (openId string, unionId string, err error) {
	req := &open_rpc.MiniOauthRequest{
		AppId: appId,
		Code:  code,
	}
	switch platform {
	case model.ChannelMallMINA, model.ChannelBgMINA:
		req.Platform = open_rpc.Platform_PLATFORM_WECHAT
	case model.ChannelMallAlipay:
		req.Platform = open_rpc.Platform_PLATFORM_ALIPAY
	case model.ChannelMallByte:
		req.Platform = open_rpc.Platform_PLATFORM_BYTEDANCE
	default:
		req.Platform = open_rpc.Platform_PLATFORM_UNSPECIFIED
	}
	conn, err := dao.openPool.Get(ctx)
	if err != nil {
		return "", "", err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := open_rpc.NewOpenServiceClient(conn.ClientConn)
	resp, err := client.OauthMini(ctx, req)
	if err != nil {
		return "", "", err
	}
	return resp.OpenId, resp.UnionId, nil
}

// AddressSearch 地址搜索
func (dao *vDaoImpl) AddressSearch(ctx context.Context, address string, lat, lng float64) ([]*object.SearchAddressInfo, error) {
	req := &admin_rpc.AddressSearchReq{
		Keyword:   address,
		Latitude:  lat,
		Longitude: lng,
	}
	conn, err := dao.adminPool.Get(ctx)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := admin_rpc.NewBilinVendorClient(conn.ClientConn)
	res, err := client.AddressSearch(ctx, req)
	if err != nil {
		return nil, err
	}
	ret := make([]*object.SearchAddressInfo, len(res.Addresses))
	for idx, addr := range res.Addresses {
		ret[idx] = &object.SearchAddressInfo{
			Title:     addr.Title,
			Address:   addr.Address,
			Category:  addr.Category,
			Latitude:  addr.Latitude,
			Longitude: addr.Longitude,
			Distance:  addr.Distance,
			CityCode:  addr.CityCode,
		}
	}
	return ret, nil
}

// LocationToAddress 坐标转地址
func (dao *vDaoImpl) LocationToAddress(ctx context.Context, lat, lng float64) (*object.LocatedAddressInfo, error) {
	req := &admin_rpc.LocationToAddressReq{
		Latitude:  lat,
		Longitude: lng,
	}
	conn, err := dao.adminPool.Get(ctx)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := admin_rpc.NewBilinVendorClient(conn.ClientConn)
	res, err := client.LocationToAddress(ctx, req)
	if err != nil {
		return nil, err
	}
	return &object.LocatedAddressInfo{
		CityCode: res.CityCode,
		Address:  res.Address,
		Nation:   res.Nation,
		Province: res.Province,
		City:     res.City,
		District: res.District,
		Street:   res.Street,
		StreetNo: res.StreetNo,
	}, nil
}

// AddressToLocation 地址转坐标解析
func (dao *vDaoImpl) AddressToLocation(ctx context.Context, address, region string) (*object.AddressParseInfo, error) {
	req := &admin_rpc.AddressToLocationReq{
		Address: address,
		Region:  region,
	}
	conn, err := dao.adminPool.Get(ctx)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := admin_rpc.NewBilinVendorClient(conn.ClientConn)
	resp, err := client.AddressToLocation(ctx, req)
	if err != nil {
		return nil, err
	}
	return &object.AddressParseInfo{
		Title:     resp.Title,
		Latitude:  resp.Latitude,
		Longitude: resp.Longitude,
		CityCode:  resp.CityCode,
		Province:  resp.Province,
		City:      resp.City,
		District:  resp.District,
		Street:    resp.Street,
		StreetNo:  resp.StreetNo,
	}, nil
}

// GetMinaEncryptedMobile 手机号解密
func (dao *vDaoImpl) GetMinaEncryptedMobile(ctx context.Context, appId, openId, encryptedData, iv string, platform int8) (mobile string, countryCode string, err error) {
	req := &open_rpc.DecryptMiniDataRequest{
		AppId:         appId,
		OpenId:        openId,
		EncryptedData: encryptedData,
		Iv:            iv,
	}
	switch platform {
	case model.ChannelMallMINA, model.ChannelBgMINA:
		req.Platform = open_rpc.Platform_PLATFORM_WECHAT
	case model.ChannelMallAlipay:
		req.Platform = open_rpc.Platform_PLATFORM_ALIPAY
	case model.ChannelMallByte:
		req.Platform = open_rpc.Platform_PLATFORM_BYTEDANCE
	default:
		req.Platform = open_rpc.Platform_PLATFORM_UNSPECIFIED
	}
	conn, err := dao.openPool.Get(ctx)
	if err != nil {
		return "", "", err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := open_rpc.NewOpenServiceClient(conn.ClientConn)
	res, err := client.DecryptMiniData(ctx, req)
	if err != nil {
		return "", "", err
	}
	type data struct {
		PhoneNumber     string `json:"phoneNumber"`
		PurePhoneNumber string `json:"purePhoneNumber"`
		CountryCode     string `json:"countryCode"`
	}
	d := &data{}
	err = json.Unmarshal(res.DecryptedData, d)
	if err != nil {
		return "", "", err
	}
	return d.PurePhoneNumber, d.CountryCode, nil
}
